查看原文
其他

Envoy service mesh、Prometheus和Grafana下的微服务监控

Arvind 几米宋 2022-09-07

作者 Arvind Thangamani

译者 蒙奕锟

审校者 杨传胜

3100字 | 阅读大约需要6分钟

如果你刚接触“Service Mesh“和“Envoy”,我这里有一篇文章可以帮你入门。

这是Envoy service mesh下的可观测性系列的第二篇文章,你可以在这里阅读第一篇关于分布式追踪的文章。

在微服务中谈及监控时,你可不能被蒙在鼓里,至少要知道问题出在哪儿了。

让我们看看Envoy是怎样帮助我们了解我们的服务运行状况的。在service mesh下,所有的通信都会通过mesh,这意味着没有任何服务会与其它服务直接通信,服务向Envoy发起调用请求,然后Envoy将调用请求路由到目标服务,所以Envoy将持有传入和传出流量的上下文。Envoy通常提供关于传入请求、传出请求和Envoy实例状态的指标。

准备

这是我们将要构建的系统概览。

Statsd

Envoy支持通过两到三种格式来暴露指标,但本文中我们将使用statsd格式。

所以流程将是这样,首先Envoy推送指标到statsd,然后我们用prometheus(一个时序数据库)从statsd拉取指标,最后通过grafana可视化这些指标。

在准备概览图中,我提到了statsd exporter而不是statsd,这是因为我们并不会直接使用statsd,而是使用一个接收statsd格式数据,并将其以prometheus格式输出的转换器(服务)。下面让我们来搞定它吧。

Envoy的指标主要分为两类:

  1. Counter(计数器):一个只增不减的指标。如:请求总数

  2. Gauge(量表):一个可增可减的指标,类似于一个瞬时值。如:当前CPU使用量

让我们看一个包含stats sink的Envoy配置

  1. ---

  2. admin:

  3.  access_log_path: "/tmp/admin_access.log"

  4.  address:

  5.    socket_address:

  6.      address: "127.0.0.1"

  7.      port_value: 9901

  8. stats_sinks:

  9.  -

  10.    name: "envoy.statsd"

  11.    config:

  12.      tcp_cluster_name: "statsd-exporter"

  13.      prefix: front-envoy    

  14. static_resources:

  15.  listeners:

  16.    -

  17.      name: "http_listener"

  18.      address:

  19.        socket_address:

  20.          address: "0.0.0.0"

  21.          port_value: 80

  22.      filter_chains:

  23.          filters:

  24.            -

  25.              name: "envoy.http_connection_manager"

  26.              config:

  27.                use_remote_address: true

  28.                add_user_agent: true

  29.                access_log:

  30.                - name: envoy.file_access_log

  31.                  config:

  32.                    path: /dev/stdout

  33.                    format: "[ACCESS_LOG][%START_TIME%] \"%REQ(:METHOD)% %REQ(X-ENVOY-ORIGINAL-PATH?:PATH)% %PROTOCOL%\" %RESPONSE_CODE% %RESPONSE_FLAGS% %BYTES_RECEIVED% %BYTES_SENT% %DURATION% %RESP(X-ENVOY-UPSTREAM-SERVICE-TIME)% \"%REQ(X-FORWARDED-FOR)%\" \"%REQ(USER-AGENT)%\" \"%REQ(X-REQUEST-ID)%\" \"%REQ(:AUTHORITY)%\" \"%UPSTREAM_HOST%\" \"%DOWNSTREAM_REMOTE_ADDRESS_WITHOUT_PORT%\"\n"

  34.                stat_prefix: "ingress_443"

  35.                codec_type: "AUTO"

  36.                generate_request_id: true

  37.                route_config:

  38.                  name: "local_route"

  39.                  virtual_hosts:

  40.                    -

  41.                      name: "http-route"

  42.                      domains:

  43.                        - "*"

  44.                      routes:

  45.                        -

  46.                          match:

  47.                            prefix: "/"

  48.                          route:

  49.                            cluster: "service_a"

  50.                http_filters:

  51.                  -

  52.                    name: "envoy.router"

  53.  clusters:

  54.    -

  55.      name: "statsd"

  56.      connect_timeout: "0.25s"

  57.      type: "strict_dns"

  58.      lb_policy: "ROUND_ROBIN"

  59.      hosts:

  60.        -

  61.          socket_address:

  62.            address: "statsd_exporter"

  63.            port_value: 9125

  64.    -

  65.      name: "service_a"

  66.      connect_timeout: "0.25s"

  67.      type: "strict_dns"

  68.      lb_policy: "ROUND_ROBIN"

  69.      hosts:

  70.        -

  71.          socket_address:

  72.            address: "service_a_envoy"

  73.            port_value: 8786

第8-13行告诉Envoy我们需要statsd格式的指标、我们的统计信息前缀(通常是你的服务名)是什么和statsd sink的地址。

第55-63行配置了我们的环境中的statsd sink。

这就是让Envoy输出统计信息所需要的所有配置。现在让我们来看看第2-7行做了哪些事情:

  1. Envoy在9901端口暴露了一个管理端,你可以通过它动态地改变日志级别,查看当前配置、统计数据等

  2. Envoy也可以生成与nginx类似的访问日志,你可以通过它了解服务间的通信状况。访问日志的格式也是可配置的,如第29-33行

你需要将相同的配置添加到系统中的其它Envoy sidecar上(是的,每个服务都有自己的Envoy sidecar)。

这些服务本身是用go写的,它们做的事情很简单,仅仅是通过Envoy调用其它服务。你可以在这里查看服务和Envoy的配置。

现在,虽然我们只有图中的statsd exporter,但有了它,如果我们运行docker容器(docker-compose build & docker-compose up),然后向Front Envoy(localhost:8080)发送一些流量,Envoy 将把这些流量的指标发送到statsd exporter,随后statsd exporter会把这些指标转换成prometheus格式,并将其暴露在9102端口。

Statsd exporter中的统计信息格式如下图所示

这里边将有上百个指标,同时,在上面的截图中我们能看到Service A和Service B之间的通信延迟指标。上图的指标是遵循prometheus格式的

  1. metric_name ["{" label_name "=" `"` label_value `"` { "," label_name "=" `"` label_value `"` } [ "," ] "}"] value [ timestamp ]

你可以在这里了解更多。

Prometheus

我们将使用Prometheus作为时序数据库来保存我们的指标。Prometheus不仅是一个时序数据库,它本身还是一个监控系统,但本文我们只用它来存储指标数据。需要注意的是,prometheus是一个通过主动拉取来获取指标的系统,这意味着你必须告诉prometheus从何处拉取指标,在我们的例子中是从statsd exporter处拉取。

将Prometheus添加到系统中非常简单而又直接,我们只需要将拉取目标(statsd exporter)作为配置文件传递给Prometheus就可以了。配置看起来是这样的

  1. global:

  2.  scrape_interval:  15s

  3. scrape_configs:

  4.  - job_name: 'statsd'

  5.    scrape_interval: 5s

  6.    static_configs:

  7.      - targets: ['statsd_exporter:9102']

  8.        labels:

  9.          group: 'services'

scrape_interval的值表示Prometheus从目标处拉取配置的频率。

现在启动Prometheus,里面应该有一些数据了。让我们打开localhost:9090来看一看

如图所示,可以看到我们的指标。你能做的可不仅仅是选择已有的指标,从这里可以阅读关于prometheus查询语言的更多信息。它还可以基于查询结果绘制图表,除此之外还有一个报警系统。

如果我们打开prometheus的targets页面,将能看到所有的拉取目标和它们的健康状态

Grafana

Grafana是一个很棒的监控可视化解决方案,它支持Prometheus,Graphite,InfluxDB,ElasticSearch等多种后端。

Grafana有两大主要组件需要我们配置

(1). 数据源(Datasource):指定grafana从哪个后端获取指标。你可以通过配置文件来配置数据源,代码如下所示

  1. apiVersion: 1

  2. datasources:

  3.  - name: prometheus

  4.    type: prometheus

  5.    access: Server

  6.    url: http://prometheus:9090

  7.    editable: true

(2). 仪表盘(Dashboard):你可以从仪表盘查看来自数据源的指标。Grafana支持多种可视化元素,如Graphs,Single Stats,Heatmaps……你可以继承这些元素并使用插件来构造自己的元素。

我在使用Grafana时遇到的唯一一个问题是,缺少一种标准的方法来用代码开发那些仪表盘。所幸有一些第三方的库提供了支持,我们将使用来自weaveworks的grafanalib。

下面是我们通过 python 代码尝试构建的一个仪表盘

  1. from grafanalib.core import *

  2. import os

  3. dashboard = Dashboard(

  4.    title="Services Dashboard",

  5.    templating=Templating(

  6.        [

  7.            Template(

  8.                name="source",

  9.                dataSource="prometheus",

  10.                query="metrics(.*_cluster_.*_upstream_rq_2xx)",

  11.                regex="/(.*)_cluster_.*_upstream_rq_2xx/",

  12.                default="service_a"

  13.            ),

  14.            Template(

  15.                name="destination",

  16.                dataSource="prometheus",

  17.                query="metrics(.*_cluster_.*_upstream_rq_2xx)",

  18.                regex="/.*_cluster_(.*)_upstream_rq_2xx/",

  19.                default="service_b"

  20.            )

  21.        ]

  22.    ),

  23.    rows=[

  24.        Row(

  25.            panels=[

  26.                Graph(

  27.                    title="2XX",

  28.                    transparent=True,

  29.                    dataSource="prometheus",

  30.                    targets=[

  31.                        Target(

  32.                            expr="[[source]]_cluster_[[destination]]_upstream_rq_2xx - [[source]]_cluster_[[destination]]_upstream_rq_2xx offset $__interval",

  33.                            legendFormat="2xx"

  34.                        )

  35.                    ]

  36.                ),

  37.                Graph(

  38.                    title="5XX",

  39.                    transparent=True,

  40.                    dataSource="prometheus",

  41.                    targets=[

  42.                        Target(

  43.                            expr="[[source]]_cluster_[[destination]]_upstream_rq_5xx - [[source]]_cluster_[[destination]]_upstream_rq_5xx offset $__interval",

  44.                            legendFormat="5xx"

  45.                        ),

  46.                    ]

  47.                ),

  48.                Graph(

  49.                    title="Latency",

  50.                    transparent=True,

  51.                    dataSource="prometheus",

  52.                    targets=[

  53.                        Target(

  54.                            expr="[[source]]_cluster_[[destination]]_upstream_rq_time",

  55.                            legendFormat="{{quantile}}"

  56.                        )

  57.                    ]

  58.                )

  59.            ]

  60.        ),

  61.    ]

  62. ).auto_panel_ids()

在这段代码中,我们为2xx,5xx和延迟数据构建了图表。其中第5-22行很重要,它从我们的设置中提取可用的service names作为grafana的变量,为我们创建一个动态的仪表盘,这意味着我们能够选择性地查看特定源服务和目标服务的统计数据。如果想了解更多关于变量的内容请参考这里。

你需要通过grafanalib命令来从上述python文件生成仪表盘

  1.     generate-dashboard -o dashboard.json service-dashboard.py

注意这里生成的dashboard.json可不容易阅读。

所以,启动Grafana时我们只需要传递仪表盘和数据源就好了。当访问http:localhost:3000时,你将看到:

现在你应该能看到2xx,5xx和延迟的图表,同时还能看到一个下拉菜单,你可以通过它选择源服务和目标服务。关于Grafana还有许多内容我们没有讨论到,包括强大的查询编辑器和告警系统。更重要的是,这一切都是可以通过插件和应用扩展的,可以参考这里的例子。如果你正想可视化常见服务如redis,rabbitmq等的指标,grafana有一个公共仪表盘库,你只需要导入它们就可以使用了。使用Grafana 还有一个好处,你可以通过配置文件和代码创建和管理所有东西,而不需要过多地通过UI来操作。

我建议你试用一下prometheus和grafana以了解更多信息。感谢阅读,如有建议和意见,请写在评论中。

在这里可以找到所有代码和配置文件。

活动推荐

推荐阅读

Istio免费直播课程推荐

本课程来自 IBM 微课程,通过视频直播的方式帮助您快速了解 Istio,每周一期。

  • 11月1日 Istio初探

  • 11月8日 Istio上手

  • 11月15日 Istio的安全管理

  • 11月22日 Envoy

  • 11月29日 使用Istio来监控和可视化微服务

  • 12月6日 Istio mixer - 基本概念,策略、遥测与扩展

  • 12月13日 Istio跨云管理方案解析

  • 12月20日 Istio使用案例:Serverless 平台knative

  • 详情请参考:IBM微讲堂之年度大戏《Istio系列》

点击【阅读原文】跳转到ServiceMesher网站上浏览可以查看文中的链接。

  • SOFAMesh(https://github.com/alipay/sofa-mesh)基于Istio的大规模服务网格解决方案

  • SOFAMosn(https://github.com/alipay/sofa-mosn)使用Go语言开发的高性能Sidecar代理

合作社区

参与社区

以下是参与ServiceMesher社区的方式,最简单的方式是联系我!

  • 加入微信交流群:关注本微信公众号后访问主页右下角有获取联系方式按钮,添加好友时请注明姓名-公司

  • 社区网址:http://www.servicemesher.com

  • Slack:https://servicemesher.slack.com (需要邀请才能加入)

  • GitHub:https://github.com/servicemesher

  • Istio中文文档进度追踪:https://github.com/servicemesher/istio-official-translation

  • Twitter: https://twitter.com/servicemesher

  • 提供文章线索与投稿:https://github.com/servicemesher/trans


您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存